
package simuri.disambiguation;

import java.util.HashMap;
import java.util.Set;
import java.util.TreeSet;
import org.apache.log4j.Logger;
import simuri.coherence.CoherenceMatrix;
import simuri.utils.Configuration;
import simuri.utils.EntityUriMap;
import simuri.utils.NamedEntity;

/**
 *
 * @author ngonga
 */
public class NormalizedHits implements Algorithm {
    
    // Threshold for value that are to be reduced to 0     
    public double EPSILON = 0.01;
    public double NORMDIFF = 0.01;
    // maximal number of iterations
    public double ITERATIONS_MAX = 2;
    // inflation paramater to speed up convergence
    public double R = 2;
    static Logger logger = Logger.getLogger("AEON");

    /** Implements a variation of HITS for disambiguation. TESTED
     * 
     * @param similarities Similarity vectors
     * @param c Coherence matrix
     * @param aprioriScores Apriori score vector
     * @return UriMappingSet that maps entities to the right uris
     */
    @Override
    public EntityUriMap getMapping(EntityUriMap similarities, CoherenceMatrix c,
            HashMap<String, Double> aprioriScores) {
        logger.info("Running normalized hits");
        // get normalized similarities and apriori scores        
        EntityUriMap normedSims = normalize(similarities);

        logger.info("Input Similarities=" + normedSims);
        EntityUriMap normedAprioriMap = normalize(getAprioriMap(similarities, aprioriScores));
        logger.info("Input Apriori=" + normedAprioriMap);
        //get rid of coherences between nodes set for the same entity
        c = cleanCoherenceMatrix(c, similarities);
        logger.info("Coherence matrix=" + c);
        c.normRows();
        logger.info("Coherence matrix=" + c);
        Set<NamedEntity> entities = normedSims.keySet();
        TreeSet<String> uris = normedSims.getAllUris();
        //iterate until stopping condition is reached
        EntityUriMap sims = new EntityUriMap();
        EntityUriMap ap = new EntityUriMap();
        double simValue, apValue;

        // stopping condition is either the maximal number of iterations is reached
        // or we reach a stable solution
        for (int i = 0; i < ITERATIONS_MAX; i++) {
            for (NamedEntity e : entities) {
                for (String uri : uris) {
                    simValue = 0;
                    apValue = 0;
                    for (String refUri : uris) {
                        simValue = simValue + normedAprioriMap.get(e, refUri) * c.getCoherence(uri, refUri);
                        apValue = apValue + normedSims.get(e, refUri) * c.getCoherence(uri, refUri);
                    }
                    //inflation step
                    simValue = Math.pow(simValue, R);
                    if (simValue <= EPSILON) {
                        simValue = 0;
                    }
                    apValue = Math.pow(apValue, R);
                    if (apValue <= EPSILON) {
                        apValue = 0;
                    }
                    //experimental
                    if (normedSims.get(e, uri) > 0) {
                        sims.add(e, uri, simValue + normedSims.get(e, uri));
                    }
                    if (normedAprioriMap.get(e, uri) > 0) {
                        ap.add(e, uri, apValue + normedAprioriMap.get(e, uri));
                    }
                    //original
//                    if(normedSims.get(e, uri) > 0)
//                        sims.add(e, uri, simValue);
//                    if(normedAprioriMap.get(e, uri) > 0)
//                        ap.add(e, uri, apValue);
                }
            }

            logger.info("Similarities before normalizing = " + sims);
            sims = normalize(sims);
            ap = normalize(ap);
            logger.info("=== Iteration " + (i + 1) + " ===\n" + sims + "\n" + ap);
            if (maxDiffNorm(normedSims, sims) <= NORMDIFF) {
                logger.info("Found stable solution at iteration " + (i + 1));
                logger.info("Similarities = " + sims);
                break;
            } else {
                normedSims = sims;
                normedAprioriMap = ap;
                //logger.info("Iteration "+(i+1)+":\n ==== \n");
                //logger.info("Sims = \n"+normedSims+"\n");
                //logger.info("Ap = \n"+normedAprioriMap+"\n");
            }
        }
        if (sims.size() != similarities.size()) {
            logger.info("Error while processing. Using fallback solution.");
            return new AprioriAlgorithm().getMapping(similarities, c, aprioriScores);
        } else {
            logger.info("Solution:\n" + sims);
        }
        return sims;
    }

    /** Implements a variation of HITS for disambiguation. 
     * Differs from the other implementation by the fact that it include the tree 
     * distance between entities in the dependency parse tree to improve the results
     * 
     * @param similarities Similarity vectors
     * @param c Coherence matrix
     * @param aprioriScores Apriori score vector
     * @return UriMappingSet that maps entities to the right uris
     */
    @Override
    public EntityUriMap getMapping(EntityUriMap similarities, CoherenceMatrix c,
            HashMap<String, Double> aprioriScores, Configuration config) {
        logger.info("Running normalized hits");
        // get normalized similarities and apriori scores        
        EntityUriMap normedSims = normalize(similarities);

        logger.info("Normalized Similarities\n" + normedSims);
        EntityUriMap normedAprioriMap = normalize(getAprioriMap(normedSims, aprioriScores));

        logger.info("Normalized a-priori vector\n" + normedAprioriMap);
        //get rid of coherences between nodes set for the same entity
        c = cleanCoherenceMatrix(c, similarities);
        //logger.info("Coherence matrix=" + c);
        c.normRows();
        //logger.info(">>>>>>>>>>>\nCoherence matrix=" + c);

        Set<NamedEntity> entities = normedSims.keySet();
        TreeSet<String> uris = normedSims.getAllUris();

        //get the dependency knowledge
        HashMap<NamedEntity, HashMap<NamedEntity, Double>> dependencies =
                getDependencyMatrix(config.inputText, entities);
        logger.info("Dependencies = " + dependencies);

        //iterate until stopping condition is reached
        EntityUriMap sims = new EntityUriMap();
        EntityUriMap apriori = new EntityUriMap();
        double simValue, apValue;

        // stopping condition is either the maximal number of iterations is reached
        // or we reach a stable solution
        double add;
        for (int i = 0; i < ITERATIONS_MAX; i++) {
            //compute similarity values
            for (NamedEntity e : normedSims.keySet()) {
                for (String uri : normedSims.get(e).keySet()) {
                    simValue = 0;
                    for (NamedEntity e2 : normedSims.keySet()) {
                        for (String refUri : normedSims.get(e2).keySet()) {
                            if (!e.label.equals(e2.label)) {
                                //System.out.println(e2 + ", " + refUri + " -> " + normedAprioriMap.get(e2, refUri));
                                simValue = simValue + normedAprioriMap.get(e2, refUri) * c.getCoherence(refUri, uri) / Math.pow(2, dependencies.get(e).get(e2));
                            }
                        }
                    }
                    sims.add(e, uri, normedSims.get(e, uri) + Math.pow(simValue, R));
                }
            }

            //compute apriori values
            for (NamedEntity e : normedAprioriMap.keySet()) {
                for (String uri : normedAprioriMap.get(e).keySet()) {
                    apValue = 0;
                    for (NamedEntity e2 : entities) {
                        for (String refUri : uris) {
                            if (!e.label.equals(e2.label)) {
                                apValue = normedSims.get(e, uri) * c.getCoherence(refUri, uri) / Math.pow(2, dependencies.get(e).get(e2));
                            }
                        }
                    }
                    apriori.add(e, uri, normedAprioriMap.get(e, uri) + Math.pow(apValue, R));
                }
            }

            sims = normalize(sims);
            apriori = normalize(apriori);
            logger.info("=== Iteration " + (i + 1) + " ===\n" + sims + "\n" + apriori);
            normedSims = sims;
            normedAprioriMap = apriori;
        }

        if (sims.size() != similarities.size()) {
            logger.info("Error while processing. Using fallback solution.");
            return new AprioriAlgorithm().getMapping(similarities, c, aprioriScores);
        } else {
            logger.info("Solution:\n" + sims);
        }
        return sims;
    }

    public CoherenceMatrix cleanCoherenceMatrix(CoherenceMatrix c, EntityUriMap map) {
        for (NamedEntity e : map.keySet()) {
            Set<String> uris = map.get(e).keySet();
            for (String uri1 : uris) {
                for (String uri2 : uris) {
                    if (!uri1.equals(uri2)) {
                        c.setCoherence(uri1, uri2, 0.0);
                    } else {
                        c.setCoherence(uri1, uri2, 1.0);
                    }
                }
            }
        }
        return c;
    }

    /** Computes the max norm of the matrix (a-b). Assumes that the matrices are
     * of exactly the same size
     * @param a First input matrix
     * @param b Second input matrix
     * @return Max norm
     */
    public double maxDiffNorm(EntityUriMap a, EntityUriMap b) {
        double max = 0, diff;
        for (NamedEntity e : a.keySet()) {
            for (String uri : a.getAllUris()) {
                diff = Math.abs(a.get(e, uri) - b.get(e, uri));
                //System.out.println(e + " "+ uri + " "+ diff);
                if (diff >= max) {
                    max = diff;
                }
            }
        }
        return max;
    }

    /** Normalizes a-priori vector
     * 
     */
    public static HashMap<String, Double> normalize(HashMap<String, Double> aprioriScores, EntityUriMap sims) {
        HashMap<String, Double> result = new HashMap<String, Double>();
        double score;
        for (NamedEntity e : sims.keySet()) {
            score = 0;
            for (String uri : sims.get(e).keySet()) {
                score = score + sims.get(e, uri);
            }

            for (String uri : sims.get(e).keySet()) {
                result.put(uri, sims.get(e, uri) / score);
            }
        }
        return result;
    }

    /** Transforms the apriori scores into a map that maps each named entity to
     * corresponding uris, of which each is mapped to the right apriori score. By
     * this means, the computation of HITS is easier to implement
     * @param sim List of known similarities between uris and named entities
     * @param aprioriScores A priori score for each URI
     * @return EntityUriMap that maps each entity to a URI with the apriori 
     * score of the entity
     */
    public static EntityUriMap getAprioriMap(EntityUriMap sim, HashMap<String, Double> aprioriScores) {
        EntityUriMap result = new EntityUriMap();
        HashMap<String, Double> apriori;
        for (NamedEntity e : sim.keySet()) {
            apriori = new HashMap<String, Double>();
            HashMap<String, Double> uriMap = sim.get(e);
            for (String uri : uriMap.keySet()) {
                if (aprioriScores.containsKey(uri)) {
                    apriori.put(uri, aprioriScores.get(uri));
                } else {
                    logger.warn("getNormalizedApriori: Could not find " + uri);
                }
            }
            result.put(e, apriori);
        }
        return result;
    }

    /** Normalizes the map according to the total sum of weights
     * 
     * @param map Input map
     * @return Normalized map
     */
    public static EntityUriMap normalize(EntityUriMap map) {
        double sum;
        EntityUriMap result = new EntityUriMap();

        //for each named entity
        for (NamedEntity e : map.keySet()) {
            //compute the total sum of weights
            sum = 0;
            HashMap<String, Double> uriMap = map.get(e);
            for (String uri : uriMap.keySet()) {
                sum = sum + uriMap.get(uri);
            }

            //norm the weights
            HashMap<String, Double> copy = new HashMap<String, Double>();
            for (String uri : uriMap.keySet()) {
                copy.put(uri, uriMap.get(uri) / sum);
            }

            //write the result in new map
            result.put(e, copy);
        }

        //return the new map
        return result;
    }

    /** Computes the dependency between entities 
     * 
     * @param input Text input
     * @param entities Set of entities to be considered
     * @return Distance matrix of the entities
     */
    private static HashMap<NamedEntity, HashMap<NamedEntity, Double>> getDependencyMatrix(String input,
            Set<NamedEntity> entities) {
        //Tree dependencies = StanfordParser.getDependencies(input,
        //        "D:/Work/Java/Aeon/resources/englishFactored.ser.gz");
        //logger.info(dependencies);
        HashMap<NamedEntity, HashMap<NamedEntity, Double>> matrix = new HashMap<NamedEntity, HashMap<NamedEntity, Double>>();

        for (NamedEntity ne : entities) {
            HashMap<NamedEntity, Double> map = new HashMap<NamedEntity, Double>();
            for (NamedEntity ne2 : entities) {
                try {
                    //map.put(ne2, (double) dependencies.getDistance(dependencies.getElement(ne.label, ne.offset, input),
                    //        dependencies.getElement(ne2.label, ne2.offset, input)));
                    map.put(ne2, 1.0);
                } catch (Exception e) {
                    logger.warn("Something went wrong with the parsing. Using default");
                    map.put(ne2, 1.0);
                }
            }
            matrix.put(ne, map);
        }
        return matrix;
    }

    public static void main(String args[]) {

        String input = "Reportedly, the book describes his childhood in Australia; how he came to love computers and became obsessed with hacking; the founding of Wikileaks; and events leading to sexual assault charges against him in Sweden. Assange told Canongate that the book might be used by US authorities seeking to extradite him on possible Wikileaks-related espionage charges.";
        NamedEntity e1 = new NamedEntity("US", 272);
        NamedEntity e2 = new NamedEntity("Australia", 48);
        NamedEntity e3 = new NamedEntity("Canongate", 231);
        NamedEntity e4 = new NamedEntity("Sweden", 210);
        TreeSet<NamedEntity> entities = new TreeSet<NamedEntity>();
        entities.add(e1);
        entities.add(e2);
        entities.add(e3);
        entities.add(e4);

        System.out.println(getDependencyMatrix(input, entities));
        //        //test for normalized entityurimaps
//        EntityUriMap sim = new EntityUriMap();
//        NamedEntity e1 = new NamedEntity("a", 1);
//        sim.add(e1, "uri1", 0.25);
//        sim.add(e1, "uri2", 0.75);
//        NamedEntity e2 = new NamedEntity("b", 1);        
//        sim.add(e2, "uri3", 1.0);
//        sim = normalize(sim);
//        logger.info("Similarities:\n"+sim+"\n");
//
//        //test for getAprioriMaps
//        HashMap<String, Double> ap = new HashMap<String, Double>();
//        ap.put("uri1", 0.8);
//        ap.put("uri2", 0.2);
//        ap.put("uri3", 1.0);
//        logger.info("A priori:\n"+normalize(getAprioriMap(sim, ap)));
//        
//        // coherence matrix
//        SparseCoherenceMatrix cm = new SparseCoherenceMatrix();
//        cm.setCoherence("uri1", "uri3", 0.75);
//        cm.setCoherence("uri2", "uri3", 0.25);
//        cm.setCoherence("uri3", "uri1", 0.75);
//        cm.setCoherence("uri3", "uri2", 0.25);
//        cm.setCoherence("uri1", "uri1", 1.0);
//        cm.setCoherence("uri2", "uri2", 1.0);
//        cm.setCoherence("uri3", "uri3", 1.0);
//        cm.setCoherence("uri1", "uri2", 1.0);
//        logger.info("Coherence matrix:\n"+cm);
//        
//        //test algorithm
//        NormalizedHits nh = new NormalizedHits();
//        cm = (SparseCoherenceMatrix)nh.cleanCoherenceMatrix(cm, sim);
//        logger.info("Clean coherence matrix:\n"+cm);
//        
//        EntityUriMap result = nh.getMapping(sim, cm, ap);
//        logger.info("Result = \n"+result);

    }
    
}
